package com.huqing.icu.base.redis;

import com.huqing.icu.base.redis.proxy.RedisOperationProxy;
import com.huqing.icu.exceptions.UnexpectedException;
import com.huqing.icu.utils.JsonUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import redis.clients.jedis.SortingParams;
import redis.clients.jedis.Tuple;

import java.util.*;
import java.util.Map.Entry;


/**
 * Redis
 *
 * @author KingsHunter
 * @createDate Dec 23rd,2015
 */
@Component
public final class Redis {

    @Autowired
    private RedisOperationProxy redisOperationProxy;

    public boolean lock(String key, String value, long timeout) {
        return "OK".equals(redisOperationProxy.getOperation().set(key, value, "NX", "PX", timeout));
    }

    public boolean unlock(String key, String value) {
        String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                "return redis.call('del', KEYS[1]) else return 0 end";
        return redisOperationProxy.getOperation().eval(luaScript, Collections.singletonList(key), Collections.singletonList(value)).equals(1L);
    }

    public Object eval(String luaScript, List<String> keyList, List<String> paramList) {
        return redisOperationProxy.getOperation().eval(luaScript, keyList, paramList);
    }

    /**
     * Set the string value as value of the key. The string can't be longer than 1073741824 bytes (1
     * GB).
     * Time complexity: O(1)
     *
     * @param key
     * @param value
     * @return
     */
    public String set(final String key, final String value) {
        return redisOperationProxy.getOperation().set(key, value);
    }

    /**
     * Set the string value as value of the key. The string can't be longer than 1073741824 bytes (1
     * GB).
     * Time complexity: O(1)
     *
     * @param key
     * @param value
     * @return
     */
    public <T> String set(final String key, final T t) {
        validate(t);
        String value = JsonUtils.obj2String(t);
        return set(key, value);
    }

    /**
     * Set the string value as value of the key. The string can't be longer than 1073741824 bytes (1
     * GB).
     *
     * @param key
     * @param value
     * @param nxxx  NX|XX, NX -- Only set the key if it does not already exist. XX -- Only set the key
     *              if it already exist.
     * @param expx  EX|PX, expire time units: EX = seconds; PX = milliseconds
     * @param time  expire time in the units of <code>expx</code>
     * @return Status code reply
     */
    public String set(final String key, final String value, final String nxxx, final String expx,
                      final long time) {
        return redisOperationProxy.getOperation().set(key, value, nxxx, expx, time);
    }

    /**
     * add the string value as value of key. The string can't be longer than 1073741824 bytes (1
     * GB).
     *
     * @param key
     * @param value
     * @param time(seconds)
     * @return
     */
    public String add(final String key, final String value, final String expx, final long time) {
        return set(key, value, "NX", expx, time);
    }

    /**
     * Set the string value as value of the key. The string can't be longer than 1073741824 bytes (1
     * GB).
     *
     * @param key
     * @param value
     * @param nxxx  NX|XX, NX -- Only set the key if it does not already exist. XX -- Only set the key
     *              if it already exist.
     * @param expx  EX|PX, expire time units: EX = seconds; PX = milliseconds
     * @param time  expire time in the units of <code>expx</code>
     * @return Status code reply
     */
    public <T> String set(final String key, final T t, final String nxxx, final String expx, final long time) {
        validate(t);
        String value = JsonUtils.obj2String(t);
        return set(key, value, nxxx, expx, time);
    }

    /**
     * Get the value of the specified key. If the key does not exist null is returned. If the value
     * stored at key is not a string an error is returned because GET can only handle string values.
     * Time complexity: O(1)
     *
     * @param key
     * @return
     */
    public String get(final String key) {
        return redisOperationProxy.getOperation().get(key);
    }

    /**
     * Get the value of the specified key. If the key does not exist null is returned. If the value
     * stored at key is not a string an error is returned because GET can only handle string values.
     * Time complexity: O(1)
     *
     * @param key
     * @param clazz
     * @return
     */
    public <T> T get(final String key, Class<T> clazz) {
        validate(clazz);
        T t = null;
        String text = get(key);
        if (StringUtils.isNotBlank(text)) {
            t = JsonUtils.getClazz(text, clazz);
        }
        return t;
    }

    /**
     * Test if the specified key exists. The command returns the number of keys existed
     * Time complexity: O(N)
     *
     * @param keys
     * @return Integer reply, specifically: an integer greater than 0 if one or more keys were removed
     * 0 if none of the specified key existed
     */
    public Long exists(final String... keys) {
        return redisOperationProxy.getOperation().exists(keys);
    }

    /**
     * Test if the specified key exists. The command returns "1" if the key exists, otherwise "0" is
     * returned. Note that even keys set with an empty string as value will return "1". Time
     * complexity: O(1)
     *
     * @param key
     * @return Boolean reply, true if the key exists, otherwise false
     */
    public Boolean exists(final String key) {
        return redisOperationProxy.getOperation().exists(key);
    }

    public Long del(final List<String> keys) {
        String[] keyArr = generate(keys);
        return redisOperationProxy.getOperation().del(keyArr);
    }


    public Long delValueInList(String key, String param) {
        return redisOperationProxy.getOperation().delValueInList(key, param);
    }

    public Long lpopBatch(String key, String param) {
        return redisOperationProxy.getOperation().lpopBatch(key, param);
    }


    /**
     * Remove the specified keys. If a given key does not exist no operation is performed for this
     * key. The command returns the number of keys removed. Time complexity: O(1)
     *
     * @param keys
     * @return Integer reply, specifically: an integer greater than 0 if one or more keys were removed
     * 0 if none of the specified key existed
     */
    public Long del(final String... keys) {
        return redisOperationProxy.getOperation().del(keys);
    }

    /**
     * @param key
     * @return
     */
    public Long del(final String key) {
        return redisOperationProxy.getOperation().del(key);
    }

    /**
     * Return the type of the value stored at key in form of a string. The type can be one of "none",
     * "string", "list", "set". "none" is returned if the key does not exist. Time complexity: O(1)
     *
     * @param key
     * @return Status code reply, specifically: "none" if the key does not exist "string" if the key
     * contains a String value "list" if the key contains a List value "set" if the key
     * contains a Set value "zset" if the key contains a Sorted Set value "hash" if the key
     * contains a Hash value
     */
    public String type(final String key) {
        return null;
    }

    /**
     * Set the the respective keys to the respective values. MSET will replace old values with new
     * values, while {@link #msetnx(String...) MSETNX} will not perform any operation at all even if
     * just a single key already exists.
     * <p>
     * Because of this semantic MSETNX can be used in order to set different keys representing
     * different fields of an unique logic object in a way that ensures that either all the fields or
     * none at all are set.
     * <p>
     * Both MSET and MSETNX are atomic operations. This means that for instance if the keys A and B
     * are modified, another client talking to Redis can either see the changes to both A and B at
     * once, or no modification at all.
     *
     * @param keysvalues
     * @return Status code reply Basically +OK as MSET can't fail
     * @see #msetnx(String...)
     */
    public String mset(final String... keysvalues) {
        return redisOperationProxy.getOperation().mset(keysvalues);
    }

    public String mset(final List<String> keyvalueList) {
        if (CollectionUtils.isNotEmpty(keyvalueList)) {
            String keyvalue;
            String[] keyvalues = {};
            int keyvalueListSize = keyvalueList.size();
            for (int i = 0; i < keyvalueListSize; i++) {
                keyvalue = keyvalueList.get(i);
                keyvalues = (String[]) ArrayUtils.add(keyvalues, keyvalue);
            }
            return mset(keyvalues);
        }
        return null;
    }

    public <K, T> String mset(Map<K, T> keysvalues, String prefix) {
        if (!keysvalues.isEmpty() && StringUtils.isNotBlank(prefix)) {
            Set<Entry<K, T>> entrySet = keysvalues.entrySet();
            String key;
            String value;
            K k;
            T t;
            List<String> keyvalueList = new ArrayList<>();
            for (Entry<K, T> entry : entrySet) {
                k = entry.getKey();
                t = entry.getValue();
                validate(t);
                key = prefix + k;
                if (!t.getClass().equals(String.class))
                    value = JsonUtils.obj2String(t);
                else
                    value = (String) t;
                keyvalueList.add(key);
                keyvalueList.add(value);
            }
            return mset(keyvalueList);
        }
        return null;
    }

    /**
     * Get the values of all the specified keys. If one or more keys dont exist or is not of type
     * String, a 'nil' value is returned instead of the value of the specified key, but the operation
     * never fails.
     * <p>
     * Time complexity: O(1) for every key
     *
     * @param keys
     * @return Multi bulk reply
     */
    public List<String> mget(final String... keys) {
        validate(keys);
        int len = keys.length;
        if (len > 100)
            return Collections.emptyList();
        return redisOperationProxy.getOperation().mget(keys);
    }

    public List<String> mget(final List<String> keyList) {
        validate(keyList);
        String[] keyArr = {};
        keyArr = keyList.toArray(keyArr);
        return mget(keyArr);
    }

    public Map<String, String> mget(final String[] keys, final String prefix) {
        validate(keys);
        List<String> strList = mget(keys);
        if (CollectionUtils.isNotEmpty(strList)) {
            Map<String, String> keyOffsetValueMapper = new HashMap<String, String>();
            String text;
            String offset;
            int strListSize = strList.size();
            for (int i = 0; i < strListSize; i++) {
                text = strList.get(i);
                offset = StringUtils.removeStart(keys[i], prefix);
                keyOffsetValueMapper.put(offset, text);
            }
            return keyOffsetValueMapper;
        }

        return Collections.emptyMap();
    }

    public Map<String, String> mget(final List<String> keyList, String prefix) {
        validate(keyList);
        List<String> strList = mget(keyList);
        if (CollectionUtils.isNotEmpty(strList)) {
            Map<String, String> keyOffsetValueMapper = new HashMap<String, String>();
            String text;
            String offset;
            int strListSize = strList.size();
            for (int i = 0; i < strListSize; i++) {
                text = strList.get(i);
                offset = StringUtils.removeStart(keyList.get(i), prefix);
                keyOffsetValueMapper.put(offset, text);
            }
            return keyOffsetValueMapper;
        }

        return Collections.emptyMap();
    }

    public <K, T> Map<K, T> mget(final K[] keyOffsetArr, final String prefix, Class<K> keyOffsetClazz, Class<T> valueClazz) {
        validate(keyOffsetArr);
        keyValidate(keyOffsetClazz);
        valueValidate(valueClazz);

        String[] keyArr = {};
        int keyOffsetArrLen = keyOffsetArr.length;
        for (int i = 0; i < keyOffsetArrLen; i++)
            keyArr = (String[]) ArrayUtils.add(keyArr, prefix + keyOffsetArr[i]);

        List<String> strList = mget(keyArr);
        if (CollectionUtils.isNotEmpty(strList)) {
            Map<K, T> keyOffsetValueMapper = new HashMap<K, T>();
            String text;
            K offset;
            T t;
            int strListSize = strList.size();
            for (int i = 0; i < strListSize; i++) {
                text = strList.get(i);
                if (keyOffsetClazz.equals(Integer.class))
                    offset = (K) Integer.valueOf(StringUtils.removeStart(keyArr[i], prefix));
                else if (keyOffsetClazz.equals(Long.class))
                    offset = (K) Long.valueOf(StringUtils.removeStart(keyArr[i], prefix));
                else if (keyOffsetClazz.equals(Double.class))
                    offset = (K) Double.valueOf(StringUtils.removeStart(keyArr[i], prefix));
                else
                    offset = (K) StringUtils.removeStart(keyArr[i], prefix);
                if (!valueClazz.equals(String.class))
                    t = JsonUtils.getClazz(text, valueClazz);
                else
                    t = (T) text;
                keyOffsetValueMapper.put(offset, t);
            }
            return keyOffsetValueMapper;
        }

        return Collections.emptyMap();
    }

    public <K, T> Map<K, T> mget(final List<K> keyOffsetList, final String prefix, Class<K> keyOffsetClazz, Class<T> valueClazz) {
        validate(keyOffsetList);
        keyValidate(keyOffsetClazz);
        valueValidate(valueClazz);

        List<String> keyList = new ArrayList<>();
        int keyOffsetListSize = keyOffsetList.size();
        for (int i = 0; i < keyOffsetListSize; i++) {
            K k = keyOffsetList.get(i);
            keyList.add(prefix + k);
        }

        List<String> strList = mget(keyList);
        if (CollectionUtils.isNotEmpty(strList)) {
            Map<K, T> keyOffsetValueMapper = new HashMap<K, T>();
            String text;
            K offset;
            T t;
            int strListSize = strList.size();
            for (int i = 0; i < strListSize; i++) {
                text = strList.get(i);
                if (keyOffsetClazz.equals(Integer.class))
                    offset = (K) Integer.valueOf(StringUtils.removeStart(keyList.get(i), prefix));
                else if (keyOffsetClazz.equals(Long.class))
                    offset = (K) Long.valueOf(StringUtils.removeStart(keyList.get(i), prefix));
                else if (keyOffsetClazz.equals(Double.class))
                    offset = (K) Double.valueOf(StringUtils.removeStart(keyList.get(i), prefix));
                else
                    offset = (K) StringUtils.removeStart(keyList.get(i), prefix);
                if (!valueClazz.equals(String.class))
                    t = JsonUtils.getClazz(text, valueClazz);
                else
                    t = (T) text;
                keyOffsetValueMapper.put(offset, t);
            }
            return keyOffsetValueMapper;
        }

        return Collections.emptyMap();
    }

    /**
     * Test for existence of a specified field in a hash. <b>Time complexity:</b> O(1)
     *
     * @param key
     * @param field
     * @return Return 1 if the hash stored at key contains the specified field. Return 0 if the key is
     * not found or the field is not present.
     */
    public Boolean hexists(final String key, final String field) {
        return redisOperationProxy.getOperation().hexists(key, field);
    }

    /**
     * Set the specified hash field to the specified value.
     * <p>
     * If key does not exist, a new key holding a hash is created.
     * <p>
     * <b>Time complexity:</b> O(1)
     *
     * @param key
     * @param field
     * @param value
     * @return If the field already exists, and the HSET just produced an update of the value, 0 is
     * returned, otherwise if a new field is created 1 is returned.
     */
    public Long hset(String key, String field, String value) {
        return redisOperationProxy.getOperation().hset(key, field, value);
    }

    public <T> Long hset(String key, String field, final T t) {
        validate(t);
        String value = JsonUtils.obj2String(t);
        return redisOperationProxy.getOperation().hset(key, field, value);
    }

    /**
     * Set the respective fields to the respective values. HMSET replaces old values with new values.
     * <p>
     * If key does not exist, a new key holding a hash is created.
     * <p>
     * <b>Time complexity:</b> O(N) (with N being the number of fields)
     *
     * @param key
     * @param hash
     * @return Return OK or Exception if hash is empty
     */
    public String hmset(String key, Map<String, String> hash) {
        return redisOperationProxy.getOperation().hmset(key, hash);
    }

    /**
     * If key holds a hash, retrieve the value associated to the specified field.
     * <p>
     * If the field is not found or the key does not exist, a special 'nil' value is returned.
     * <p>
     * <b>Time complexity:</b> O(1)
     *
     * @param key
     * @param field
     * @return Bulk reply
     */
    public String hget(String key, String field) {
        String rtv = redisOperationProxy.getOperation().hget(key, field);
        if (StringUtils.isNotBlank(rtv)) {
            if ("null".equals(rtv))
                return null;
        }
        return rtv;
    }

    public <T> T hget(String key, String field, Class<T> clazz) {
        String rtv = redisOperationProxy.getOperation().hget(key, field);
        T t = null;
        if (StringUtils.isNotBlank(rtv)) {
            if ("null".equals(rtv))
                return null;
            t = JsonUtils.getClazz(rtv, clazz);
        }

        return t;
    }

    public Long hdel(String key, String... fields) {
        return redisOperationProxy.getOperation().hdel(key, fields);
    }

    /**
     * Increment the number stored at field in the hash at key by value. If key does not exist, a new
     * key holding a hash is created. If field does not exist or holds a string, the value is set to 0
     * before applying the operation. Since the value argument is signed you can use this command to
     * perform both increments and decrements.
     * <p>
     * The range of values supported by HINCRBY is limited to 64 bit signed integers.
     * <p>
     * <b>Time complexity:</b> O(1)
     *
     * @param key
     * @param field
     * @param value
     * @return Integer reply The new value at field after the increment operation.
     */
    public Long hincrBy(String key, String field, long value) {
        return redisOperationProxy.getOperation().hincrBy(key, field, value);
    }

    /**
     * Retrieve the values associated to the specified fields.
     * <p>
     * If some of the specified fields do not exist, nil values are returned. Non existing keys are
     * considered like empty hashes.
     * <p>
     * <b>Time complexity:</b> O(N) (with N being the number of fields)
     *
     * @param key
     * @param fields
     * @return Multi Bulk Reply specifically a list of all the values associated with the specified
     * fields, in the same order of the request.
     */
    public List<String> hmget(String key, String... fields) {
        List<String> strList = redisOperationProxy.getOperation().hmget(key, fields);
        int strListSize = strList.size();
        for (int i = 0; i < strListSize; i++) {
            if (StringUtils.isBlank(strList.get(i)))
                return Collections.emptyList();
        }
        return strList;
    }

    /**
     * Return all the fields and associated values in a hash.
     * <p>
     * <b>Time complexity:</b> O(N), where N is the total number of entries
     *
     * @param key
     * @return All the fields and values contained into a hash.
     */
    public Map<String, String> hgetAll(String key) {
        return redisOperationProxy.getOperation().hgetAll(key);
    }

    public <T> T hgetAll(String key, Class<T> clazz) {
        Map<String, String> fieldValueMap = hgetAll(key);
        if (fieldValueMap != null && !fieldValueMap.isEmpty()) {
            String text = JsonUtils.obj2String(fieldValueMap);
            if (StringUtils.isNotBlank(text))
                return JsonUtils.getClazz(text, clazz);
        }
        return null;
    }

    /**
     * SETNX works exactly like {@link #set(String, String) SET} with the only difference that if the
     * key already exists no operation is performed. SETNX actually means "SET if Not eXists".
     * <p>
     * Time complexity: O(1)
     *
     * @param key
     * @param value
     * @return Integer reply, specifically: 1 if the key was set 0 if the key was not set
     */
    public Long setnx(final String key, final String value) {
        return redisOperationProxy.getOperation().setnx(key, value);
    }

    /**
     * SETNX works exactly like {@link #set(String, String) SET} with the only difference that if the
     * key already exists no operation is performed. SETNX actually means "SET if Not eXists".
     * <p>
     * Time complexity: O(1)
     *
     * @param <T>
     * @param key
     * @return Integer reply, specifically: 1 if the key was set 0 if the key was not set
     */
    public <T> Long setnx(final String key, final T t) {
        validate(t);
        String value = JsonUtils.obj2String(t);
        return setnx(key, value);
    }

    /**
     * The command is exactly equivalent to the following group of commands:
     * {@link #set(String, String) SET} + {@link #expire(String, int) EXPIRE}. The operation is
     * atomic.
     * <p>
     * Time complexity: O(1)
     *
     * @param key
     * @param seconds
     * @param value
     * @return Status code reply
     */
    public String setex(final String key, final String value, final int seconds) {
        return redisOperationProxy.getOperation().setex(key, value, seconds);
    }

    /**
     * The command is exactly equivalent to the following group of commands:
     * {@link #set(String, String) SET} + {@link #expire(String, int) EXPIRE}. The operation is
     * atomic.
     * <p>
     * Time complexity: O(1)
     *
     * @param <T>
     * @param key
     * @param seconds
     * @param value
     * @return Status code reply
     */
    public <T> String setex(final String key, final T t, final int seconds) {
        validate(t);
        String value = JsonUtils.obj2String(t);
        return setex(key, value, seconds);
    }

    /**
     * Increment the number stored at key by one. If the key does not exist or contains a value of a
     * wrong type, set the key to the value of "0" before to perform the increment operation.
     * <p>
     * INCR commands are limited to 64 bit signed integers.
     * <p>
     * Note: this is actually a string operation, that is, in Redis there are not "integer" types.
     * Simply the string stored at the key is parsed as a base 10 64 bit signed integer, incremented,
     * and then converted back as a string.
     * <p>
     * Time complexity: O(1)
     *
     * @param key
     * @return Integer reply, this commands will reply with the new value of key after the increment.
     * @see #incrBy(String, long)
     * @see #decr(String)
     * @see #decrBy(String, long)
     */
    public Long incr(final String key) {
        return redisOperationProxy.getOperation().incr(key);
    }

    /**
     * INCRBY work just like {@link #incr(String) INCR} but instead to increment by 1 the increment is
     * integer.
     * <p>
     * INCR commands are limited to 64 bit signed integers.
     * <p>
     * Note: this is actually a string operation, that is, in Redis there are not "integer" types.
     * Simply the string stored at the key is parsed as a base 10 64 bit signed integer, incremented,
     * and then converted back as a string.
     * <p>
     * Time complexity: O(1)
     *
     * @param key
     * @param integer
     * @return Integer reply, this commands will reply with the new value of key after the increment.
     * @see #incr(String)
     * @see #decr(String)
     * @see #decrBy(String, long)
     */
    public Long incrBy(final String key, final long integer) {
        return redisOperationProxy.getOperation().incrBy(key, integer);
    }

    /**
     * Decrement the number stored at key by one. If the key does not exist or contains a value of a
     * wrong type, set the key to the value of "0" before to perform the decrement operation.
     * <p>
     * INCR commands are limited to 64 bit signed integers.
     * <p>
     * Note: this is actually a string operation, that is, in Redis there are not "integer" types.
     * Simply the string stored at the key is parsed as a base 10 64 bit signed integer, incremented,
     * and then converted back as a string.
     * <p>
     * Time complexity: O(1)
     *
     * @param key
     * @return Integer reply, this commands will reply with the new value of key after the increment.
     * @see #incr(String)
     * @see #incrBy(String, long)
     * @see #decrBy(String, long)
     */
    public Long decr(final String key) {
        return redisOperationProxy.getOperation().decr(key);
    }

    /**
     * IDECRBY work just like {@link #decr(String) INCR} but instead to decrement by 1 the decrement
     * is integer.
     * <p>
     * INCR commands are limited to 64 bit signed integers.
     * <p>
     * Note: this is actually a string operation, that is, in Redis there are not "integer" types.
     * Simply the string stored at the key is parsed as a base 10 64 bit signed integer, incremented,
     * and then converted back as a string.
     * <p>
     * Time complexity: O(1)
     *
     * @param key
     * @param integer
     * @return Integer reply, this commands will reply with the new value of key after the increment.
     * @see #incr(String)
     * @see #decr(String)
     * @see #incrBy(String, long)
     */
    public Long decrBy(final String key, final long integer) {
        return redisOperationProxy.getOperation().decrBy(key, integer);
    }

    public Long llen(String key) {
        return redisOperationProxy.getOperation().llen(key);
    }

    public Long lpush(String key, String... strings) {
        return redisOperationProxy.getOperation().lpush(key, strings);
    }

    public Long rpush(String key, String... strings) {
        return redisOperationProxy.getOperation().rpush(key, strings);
    }

    public <T> Long lpush(String key, T... ts) {
        validate(ts);
        String[] array = generate(ts);
        return lpush(key, array);
    }

    public <T> Long rpush(String key, T... ts) {
        validate(ts);
        String[] array = generate(ts);
        return rpush(key, array);
    }

    public <T> Long lpush(String key, List<T> list) {
        validate(list);
        String[] array = generate(list);
        return lpush(key, array);
    }

    public <T> Long rpush(String key, List<T> list) {
        validate(list);
        String[] array = generate(list);
        return rpush(key, array);
    }

    public Long rpushx(String key, String... strings) {
        return redisOperationProxy.getOperation().rpushx(key, strings);
    }

    public <T> Long rpushx(String key, T... ts) {
        validate(ts);
        String[] array = generate(ts);
        return rpushx(key, array);
    }

    public <T> Long rpushx(String key, List<T> list) {
        validate(list);
        String[] array = generate(list);
        return rpushx(key, array);
    }

    public List<String> lrange(final String key, final long start, final long end) {
        return redisOperationProxy.getOperation().lrange(key, start, end);
    }

    public <T> List<T> lrange(final String key, final long start, final long end, final Class<T> clazz) {
        List<String> strList = lrange(key, start, end);
        if (CollectionUtils.isNotEmpty(strList)) {
            List<T> list = new ArrayList<T>();
            int size = strList.size();
            T t = null;
            String text = null;
            for (int i = 0; i < size; i++) {
                text = strList.get(i);
                t = JsonUtils.getClazz(text, clazz);
                list.add(t);
            }
            return list;
        }
        return Collections.emptyList();
    }

    /**
     * Atomically return and remove the first (LPOP) or last (RPOP) element of the list. For example
     * if the list contains the elements "a","b","c" LPOP will return "a" and the list will become
     * "b","c".
     * <p>
     * If the key does not exist or the list is already empty the special value 'nil' is returned.
     *
     * @param key
     * @return Bulk reply
     * @see #rpop(String)
     */
    public String lpop(final String key) {
        return redisOperationProxy.getOperation().lpop(key);
    }

    /**
     * Remove the first count occurrences of the value element from the list. If count is zero all the
     * elements are removed. If count is negative elements are removed from tail to head, instead to
     * go from head to tail that is the normal behaviour. So for example LREM with count -2 and hello
     * as value to remove against the list (a,b,c,hello,x,hello,hello) will lave the list
     * (a,b,c,hello,x). The number of removed elements is returned as an integer, see below for more
     * information about the returned value. Note that non existing keys are considered like empty
     * lists by LREM, so LREM against non existing keys will always return 0.
     * <p>
     * Time complexity: O(N) (with N being the length of the list)
     *
     * @param key
     * @param count
     * @param value
     * @return Integer Reply, specifically: The number of removed elements if the operation succeeded
     */
    public Long lrem(final String key, final long count, final String value) {
        return redisOperationProxy.getOperation().lrem(key, count, value);
    }

    /**
     * Trim an existing list so that it will contain only the specified range of elements specified.
     * Start and end are zero-based indexes. 0 is the first element of the list (the list head), 1 the
     * next element and so on.
     * <p>
     * For example LTRIM foobar 0 2 will modify the list stored at foobar key so that only the first
     * three elements of the list will remain.
     * <p>
     * start and end can also be negative numbers indicating offsets from the end of the list. For
     * example -1 is the last element of the list, -2 the penultimate element and so on.
     * <p>
     * Indexes out of range will not produce an error: if start is over the end of the list, or start
     * &gt; end, an empty list is left as value. If end over the end of the list Redis will threat it
     * just like the last element of the list.
     * <p>
     * Hint: the obvious use of LTRIM is together with LPUSH/RPUSH. For example:
     * <p>
     * {@code lpush("mylist", "someelement"); ltrim("mylist", 0, 99); * }
     * <p>
     * The above two commands will push elements in the list taking care that the list will not grow
     * without limits. This is very useful when using Redis to store logs for example. It is important
     * to note that when used in this way LTRIM is an O(1) operation because in the average case just
     * one element is removed from the tail of the list.
     * <p>
     * Time complexity: O(n) (with n being len of list - len of range)
     *
     * @param key
     * @param start
     * @param end
     * @return Status code reply
     */
    public String ltrim(final String key, final long start, final long end) {
        return redisOperationProxy.getOperation().ltrim(key, start, end);
    }

    public Long zadd(final String key, final double score, final String member) {
        return redisOperationProxy.getOperation().zadd(key, score, member);
    }

    public <T> Long zadd(final String key, final double score, final T t) {
        validate(t);
        String member = JsonUtils.obj2String(t);
        return redisOperationProxy.getOperation().zadd(key, score, member);
    }

    public Long zadd(final String key, final Map<String, Double> scoreMembers) {
        return redisOperationProxy.getOperation().zadd(key, scoreMembers);
    }

    public Long zcount(final String key, final String min, final String max) {
        return redisOperationProxy.getOperation().zcount(key, min, max);
    }

    /**
     * Return the sorted set cardinality (number of elements). If the key does not exist 0 is
     * returned, like for empty sorted sets.
     * <p>
     * Time complexity O(1)
     *
     * @param key
     * @return the cardinality (number of elements) of the set as an integer.
     */
    public Long zcard(final String key) {
        return redisOperationProxy.getOperation().zcard(key);
    }

    /**
     * If member already exists in the sorted set adds the increment to its score and updates the
     * position of the element in the sorted set accordingly. If member does not already exist in the
     * sorted set it is added with increment as score (that is, like if the previous score was
     * virtually zero). If key does not exist a new sorted set with the specified member as sole
     * member is crated. If the key exists but does not hold a sorted set value an error is returned.
     * <p>
     * The score value can be the string representation of a double precision floating point number.
     * It's possible to provide a negative value to perform a decrement.
     * <p>
     * For an introduction to sorted sets check the Introduction to Redis data types page.
     * <p>
     * Time complexity O(log(N)) with N being the number of elements in the sorted set
     *
     * @param key
     * @param score
     * @param member
     * @return The new score
     */
    public Double zincrby(final String key, final double score, final String member) {
        return redisOperationProxy.getOperation().zincrby(key, score, member);
    }

    public Set<String> zrange(final String key, final long start, final long end) {
        return redisOperationProxy.getOperation().zrange(key, start, end);
    }

    public <T> List<T> zrange(final String key, final long start, final long end, Class<T> clazz) {
        Set<String> strSet = zrange(key, start, end);
        if (CollectionUtils.isNotEmpty(strSet)) {
            List<T> list = new ArrayList<T>();
            T t = null;
            for (String text : strSet) {
                t = JsonUtils.getClazz(text, clazz);
                list.add(t);
            }
            return list;
        }
        return Collections.emptyList();
    }

    public Set<Tuple> zrangeWithScores(String key, long start, long end) {
        return redisOperationProxy.getOperation().zrangeWithScores(key, start, end);
    }

    public Set<String> zrangeByLex(final String key, final String min, final String max) {
        return redisOperationProxy.getOperation().zrangeByLex(key, min, max);
    }

    public <T> List<T> zrangeByLex(final String key, final String min, final String max, Class<T> clazz) {
        Set<String> strSet = zrangeByLex(key, min, max);
        if (CollectionUtils.isNotEmpty(strSet)) {
            List<T> list = new ArrayList<T>();
            T t = null;
            for (String text : strSet) {
                t = JsonUtils.getClazz(text, clazz);
                list.add(t);
            }
            return list;
        }
        return Collections.emptyList();
    }

    public Set<String> zrangeByLex(final String key, final String min, final String max,
                                   final int offset, final int count) {
        return redisOperationProxy.getOperation().zrangeByLex(key, min, max, offset, count);
    }

    public <T> List<T> zrangeByLex(final String key, final String min, final String max,
                                   final int offset, final int count, Class<T> clazz) {
        Set<String> strSet = zrangeByLex(key, min, max, offset, count);
        if (CollectionUtils.isNotEmpty(strSet)) {
            List<T> list = new ArrayList<T>();
            T t = null;
            for (String text : strSet) {
                t = JsonUtils.getClazz(text, clazz);
                list.add(t);
            }
            return list;
        }
        return Collections.emptyList();
    }

    public Set<String> zrevrangeByLex(String key, String max, String min) {
        return redisOperationProxy.getOperation().zrevrangeByLex(key, max, min);
    }

    public <T> List<T> zrevrangeByLex(final String key, final String max, final String min, Class<T> clazz) {
        Set<String> strSet = zrevrangeByLex(key, max, min);
        if (CollectionUtils.isNotEmpty(strSet)) {
            List<T> list = new ArrayList<T>();
            T t = null;
            for (String text : strSet) {
                t = JsonUtils.getClazz(text, clazz);
                list.add(t);
            }
            return list;
        }
        return Collections.emptyList();
    }

    public Set<String> zrevrangeByLex(String key, String max, String min, int offset, int count) {
        return redisOperationProxy.getOperation().zrevrangeByLex(key, max, min, offset, count);
    }

    public <T> List<T> zrevrangeByLex(final String key, final String max, final String min,
                                      final int offset, final int count, Class<T> clazz) {
        Set<String> strSet = zrevrangeByLex(key, max, min, offset, count);
        if (CollectionUtils.isNotEmpty(strSet)) {
            List<T> list = new ArrayList<T>();
            T t = null;
            for (String text : strSet) {
                t = JsonUtils.getClazz(text, clazz);
                list.add(t);
            }
            return list;
        }
        return Collections.emptyList();
    }

    /**
     * Return the all the elements in the sorted set at key with a score between min and max
     * (including elements with score equal to min or max).
     * <p>
     * The elements having the same score are returned sorted lexicographically as ASCII strings (this
     * follows from a property of Redis sorted sets and does not involve further computation).
     * <p>
     * Using the optional {@link #zrangeByScore(String, double, double, int, int) LIMIT} it's possible
     * to get only a range of the matching elements in an SQL-alike way. Note that if offset is large
     * the commands needs to traverse the list for offset elements and this adds up to the O(M)
     * figure.
     * <p>
     * The {@link #zcount(String, double, double) ZCOUNT} command is similar to
     * {@link #zrangeByScore(String, double, double) ZRANGEBYSCORE} but instead of returning the
     * actual elements in the specified interval, it just returns the number of matching elements.
     * <p>
     * <b>Exclusive intervals and infinity</b>
     * <p>
     * min and max can be -inf and +inf, so that you are not required to know what's the greatest or
     * smallest element in order to take, for instance, elements "up to a given value".
     * <p>
     * Also while the interval is for default closed (inclusive) it's possible to specify open
     * intervals prefixing the score with a "(" character, so for instance:
     * <p>
     * {@code ZRANGEBYSCORE zset (1.3 5}
     * <p>
     * Will return all the values with score &gt; 1.3 and &lt;= 5, while for instance:
     * <p>
     * {@code ZRANGEBYSCORE zset (5 (10}
     * <p>
     * Will return all the values with score &gt; 5 and &lt; 10 (5 and 10 excluded).
     * <p>
     * <b>Time complexity:</b>
     * <p>
     * O(log(N))+O(M) with N being the number of elements in the sorted set and M the number of
     * elements returned by the command, so if M is constant (for instance you always ask for the
     * first ten elements with LIMIT) you can consider it O(log(N))
     *
     * @param key
     * @param min a double or Double.MIN_VALUE for "-inf"
     * @param max a double or Double.MAX_VALUE for "+inf"
     * @return Multi bulk reply specifically a list of elements in the specified score range.
     * @see #zrangeByScore(String, double, double)
     * @see #zrangeByScore(String, double, double, int, int)
     * @see #zrangeByScoreWithScores(String, double, double)
     * @see #zrangeByScoreWithScores(String, String, String)
     * @see #zrangeByScoreWithScores(String, double, double, int, int)
     * @see #zcount(String, double, double)
     */
    public Set<String> zrangeByScore(final String key, final double min, final double max) {
        return redisOperationProxy.getOperation().zrangeByScore(key, min, max);
    }

    public <T> List<T> zrangeByScore(final String key, final double min, final double max, Class<T> clazz) {
        Set<String> strSet = zrangeByScore(key, min, max);
        if (CollectionUtils.isNotEmpty(strSet)) {
            List<T> list = new ArrayList<T>();
            T t = null;
            for (String text : strSet) {
                t = JsonUtils.getClazz(text, clazz);
                list.add(t);
            }
            return list;
        }
        return Collections.emptyList();
    }

    public Set<String> zrangeByScore(final String key, final String min, final String max) {
        return redisOperationProxy.getOperation().zrangeByScore(key, min, max);
    }

    public <T> List<T> zrangeByScore(final String key, final String min, final String max, Class<T> clazz) {
        Set<String> strSet = zrangeByScore(key, min, max);
        if (CollectionUtils.isNotEmpty(strSet)) {
            List<T> list = new ArrayList<T>();
            T t = null;
            for (String text : strSet) {
                t = JsonUtils.getClazz(text, clazz);
                list.add(t);
            }
            return list;
        }
        return Collections.emptyList();
    }

    /**
     * Return the all the elements in the sorted set at key with a score between min and max
     * (including elements with score equal to min or max).
     * <p>
     * The elements having the same score are returned sorted lexicographically as ASCII strings (this
     * follows from a property of Redis sorted sets and does not involve further computation).
     * <p>
     * Using the optional {@link #zrangeByScore(String, double, double, int, int) LIMIT} it's possible
     * to get only a range of the matching elements in an SQL-alike way. Note that if offset is large
     * the commands needs to traverse the list for offset elements and this adds up to the O(M)
     * figure.
     * <p>
     * The {@link #zcount(String, double, double) ZCOUNT} command is similar to
     * {@link #zrangeByScore(String, double, double) ZRANGEBYSCORE} but instead of returning the
     * actual elements in the specified interval, it just returns the number of matching elements.
     * <p>
     * <b>Exclusive intervals and infinity</b>
     * <p>
     * min and max can be -inf and +inf, so that you are not required to know what's the greatest or
     * smallest element in order to take, for instance, elements "up to a given value".
     * <p>
     * Also while the interval is for default closed (inclusive) it's possible to specify open
     * intervals prefixing the score with a "(" character, so for instance:
     * <p>
     * {@code ZRANGEBYSCORE zset (1.3 5}
     * <p>
     * Will return all the values with score &gt; 1.3 and &lt;= 5, while for instance:
     * <p>
     * {@code ZRANGEBYSCORE zset (5 (10}
     * <p>
     * Will return all the values with score &gt; 5 and &lt; 10 (5 and 10 excluded).
     * <p>
     * <b>Time complexity:</b>
     * <p>
     * O(log(N))+O(M) with N being the number of elements in the sorted set and M the number of
     * elements returned by the command, so if M is constant (for instance you always ask for the
     * first ten elements with LIMIT) you can consider it O(log(N))
     *
     * @param key
     * @param min
     * @param max
     * @return Multi bulk reply specifically a list of elements in the specified score range.
     * @see #zrangeByScore(String, double, double)
     * @see #zrangeByScore(String, double, double, int, int)
     * @see #zrangeByScoreWithScores(String, double, double)
     * @see #zrangeByScoreWithScores(String, double, double, int, int)
     * @see #zcount(String, double, double)
     */
    public Set<String> zrangeByScore(final String key, final double min, final double max,
                                     final int offset, final int count) {
        return redisOperationProxy.getOperation().zrangeByScore(key, min, max, offset, count);
    }

    public <T> List<T> zrangeByScore(final String key, final double min, final double max,
                                     final int offset, final int count, Class<T> clazz) {
        Set<String> strSet = zrangeByScore(key, min, max, offset, count);
        if (CollectionUtils.isNotEmpty(strSet)) {
            List<T> list = new ArrayList<T>();
            T t = null;
            for (String text : strSet) {
                t = JsonUtils.getClazz(text, clazz);
                list.add(t);
            }
            return list;
        }
        return Collections.emptyList();
    }

    public Set<String> zrangeByScore(final String key, final String min, final String max,
                                     final int offset, final int count) {
        return redisOperationProxy.getOperation().zrangeByScore(key, min, max, offset, count);
    }

    public <T> List<T> zrangeByScore(final String key, final String min, final String max,
                                     final int offset, final int count, Class<T> clazz) {
        Set<String> strSet = zrangeByScore(key, min, max, offset, count);
        if (CollectionUtils.isNotEmpty(strSet)) {
            List<T> list = new ArrayList<T>();
            T t = null;
            for (String text : strSet) {
                t = JsonUtils.getClazz(text, clazz);
                list.add(t);
            }
            return list;
        }
        return Collections.emptyList();
    }

    public Set<String> zrevrange(final String key, final long start, final long end) {
        return redisOperationProxy.getOperation().zrevrange(key, start, end);
    }

    public <T> List<T> zrevrange(final String key, final long start, final long end, Class<T> clazz) {
        Set<String> strSet = zrevrange(key, start, end);
        if (CollectionUtils.isNotEmpty(strSet)) {
            List<T> list = new ArrayList<T>();
            T t = null;
            for (String text : strSet) {
                t = JsonUtils.getClazz(text, clazz);
                list.add(t);
            }
            return list;
        }
        return Collections.emptyList();
    }

    public Set<Tuple> zrevrangeWithScores(String key, long start, long end) {
        return redisOperationProxy.getOperation().zrevrangeWithScores(key, start, end);
    }

    public <K, T> Map<K, T> zrevrangeWithScores(String key, long start, long end, Class<K> keyOffsetClazz, Class<T> valueClazz) {
        Set<Tuple> set = zrevrangeWithScores(key, start, end);
        if (CollectionUtils.isNotEmpty(set)) {
            Map<K, T> keyOffsetValueMapper = new LinkedHashMap<>();
            String element;
            Double score;
            K offset;
            T t;
            for (Tuple tuple : set) {
                element = tuple.getElement();
                score = tuple.getScore();
                if (!keyOffsetClazz.equals(String.class))
                    offset = JsonUtils.getClazz(element, keyOffsetClazz);
                else
                    offset = (K) element;
                if (valueClazz.equals(String.class))
                    t = (T) String.valueOf(score);
                else if (valueClazz.equals(Integer.class))
                    t = (T) new Integer(score.intValue());
                else if (valueClazz.equals(Long.class))
                    t = (T) new Long(score.longValue());
                else
                    t = (T) score;
                keyOffsetValueMapper.put(offset, t);
            }
            return keyOffsetValueMapper;
        }
        return Collections.emptyMap();
    }

    public Set<String> zrevrangeByScore(final String key, final double max, final double min) {
        return redisOperationProxy.getOperation().zrevrangeByScore(key, max, min);
    }

    public <T> List<T> zrevrangeByScore(final String key, final double max, final double min, Class<T> clazz) {
        Set<String> strSet = zrevrangeByScore(key, max, min);
        if (CollectionUtils.isNotEmpty(strSet)) {
            List<T> list = new ArrayList<T>();
            T t = null;
            for (String text : strSet) {
                t = JsonUtils.getClazz(text, clazz);
                list.add(t);
            }
            return list;
        }
        return Collections.emptyList();
    }

    public Set<String> zrevrangeByScore(final String key, final String max, final String min) {
        return redisOperationProxy.getOperation().zrevrangeByScore(key, max, min);
    }

    public <T> List<T> zrevrangeByScore(final String key, final String max, final String min, Class<T> clazz) {
        Set<String> strSet = zrevrangeByScore(key, max, min);
        if (CollectionUtils.isNotEmpty(strSet)) {
            List<T> list = new ArrayList<T>();
            T t = null;
            for (String text : strSet) {
                t = JsonUtils.getClazz(text, clazz);
                list.add(t);
            }
            return list;
        }
        return Collections.emptyList();
    }

    public Set<String> zrevrangeByScore(final String key, final double max, final double min,
                                        final int offset, final int count) {
        return redisOperationProxy.getOperation().zrevrangeByScore(key, max, min, offset, count);
    }

    public <T> List<T> zrevrangeByScore(final String key, final double max, final double min,
                                        final int offset, final int count, Class<T> clazz) {
        Set<String> strSet = zrevrangeByScore(key, max, min, offset, count);
        if (CollectionUtils.isNotEmpty(strSet)) {
            List<T> list = new ArrayList<T>();
            T t = null;
            for (String text : strSet) {
                t = JsonUtils.getClazz(text, clazz);
                list.add(t);
            }
            return list;
        }
        return Collections.emptyList();
    }

    public Set<String> zrevrangeByScore(final String key, final String max, final String min,
                                        final int offset, final int count) {
        return redisOperationProxy.getOperation().zrevrangeByScore(key, max, min, offset, count);
    }

    public <T> List<T> zrevrangeByScore(final String key, final String max, final String min,
                                        final int offset, final int count, Class<T> clazz) {
        Set<String> strSet = zrevrangeByScore(key, max, min, offset, count);
        if (CollectionUtils.isNotEmpty(strSet)) {
            List<T> list = new ArrayList<T>();
            T t = null;
            for (String text : strSet) {
                t = JsonUtils.getClazz(text, clazz);
                list.add(t);
            }
            return list;
        }
        return Collections.emptyList();
    }

    /**
     * Return the rank (or index) or member in the sorted set at key, with scores being ordered from
     * low to high.
     * <p>
     * When the given member does not exist in the sorted set, the special value 'nil' is returned.
     * The returned rank (or index) of the member is 0-based for both commands.
     * <p>
     * <b>Time complexity:</b>
     * <p>
     * O(log(N))
     *
     * @param key
     * @param member
     * @return Integer reply or a nil bulk reply, specifically: the rank of the element as an integer
     * reply if the element exists. A nil bulk reply if there is no such element.
     * @see #zrevrank(String, String)
     */
    public Long zrank(final String key, final String member) {
        return redisOperationProxy.getOperation().zrank(key, member);
    }

    public <T> Long zrank(final String key, final T t) {
        validate(t);
        String member = JsonUtils.obj2String(t);
        return zrank(key, member);
    }

    /**
     * Return the rank (or index) or member in the sorted set at key, with scores being ordered from
     * high to low.
     * <p>
     * When the given member does not exist in the sorted set, the special value 'nil' is returned.
     * The returned rank (or index) of the member is 0-based for both commands.
     * <p>
     * <b>Time complexity:</b>
     * <p>
     * O(log(N))
     *
     * @param key
     * @param member
     * @return Integer reply or a nil bulk reply, specifically: the rank of the element as an integer
     * reply if the element exists. A nil bulk reply if there is no such element.
     * @see #zrank(String, String)
     */
    public Long zrevrank(final String key, final String member) {
        return redisOperationProxy.getOperation().zrevrank(key, member);
    }

    public <T> Long zrevrank(final String key, final T t) {
        validate(t);
        String member = JsonUtils.obj2String(t);
        return zrevrank(key, member);
    }

    /**
     * Remove the specified member from the sorted set value stored at key. If member was not a member
     * of the set no operation is performed. If key does not not hold a set value an error is
     * returned.
     * <p>
     * Time complexity O(log(N)) with N being the number of elements in the sorted set
     *
     * @param key
     * @param members
     * @return Integer reply, specifically: 1 if the new element was removed 0 if the new element was
     * not a member of the set
     */
    public Long zrem(final String key, final String... members) {
        return redisOperationProxy.getOperation().zrem(key, members);
    }

    public <T> Long zrem(final String key, final T... ts) {
        validate(ts);
        String[] array = generate(ts);
        return zrem(key, array);
    }

    public <T> Long zrem(final String key, final List<T> list) {
        validate(list);
        String[] array = generate(list);
        return zrem(key, array);
    }

    public Long zremrangeByLex(final String key, final String min, final String max) {
        return redisOperationProxy.getOperation().zremrangeByLex(key, min, max);
    }

    /**
     * Remove all elements in the sorted set at key with rank between start and end. Start and end are
     * 0-based with rank 0 being the element with the lowest score. Both start and end can be negative
     * numbers, where they indicate offsets starting at the element with the highest rank. For
     * example: -1 is the element with the highest score, -2 the element with the second highest score
     * and so forth.
     * <p>
     * <b>Time complexity:</b> O(log(N))+O(M) with N being the number of elements in the sorted set
     * and M the number of elements removed by the operation
     */
    public Long zremrangeByRank(final String key, final long start, final long end) {
        return redisOperationProxy.getOperation().zremrangeByRank(key, start, end);
    }

    /**
     * Remove all the elements in the sorted set at key with a score between min and max (including
     * elements with score equal to min or max).
     * <p>
     * <b>Time complexity:</b>
     * <p>
     * O(log(N))+O(M) with N being the number of elements in the sorted set and M the number of
     * elements removed by the operation
     *
     * @param key
     * @param start
     * @param end
     * @return Integer reply, specifically the number of elements removed.
     */
    public Long zremrangeByScore(final String key, final double start, final double end) {
        return redisOperationProxy.getOperation().zremrangeByScore(key, start, end);
    }

    public Long zremrangeByScore(final String key, final String start, final String end) {
        return redisOperationProxy.getOperation().zremrangeByScore(key, start, end);
    }

    /**
     * Return the score of the specified element of the sorted set at key. If the specified element
     * does not exist in the sorted set, or the key does not exist at all, a special 'nil' value is
     * returned.
     * <p>
     * <b>Time complexity:</b> O(1)
     *
     * @param key
     * @param member
     * @return the score
     */
    public Double zscore(final String key, final String member) {
        return redisOperationProxy.getOperation().zscore(key, member);
    }

    public Long pttl(final String key) {
        return redisOperationProxy.getOperation().pttl(key);
    }

    /**
     * The TTL command returns the remaining time to live in seconds of a key that has an
     * {@link #expire(String, int) EXPIRE} set. This introspection capability allows a Redis client to
     * check how many seconds a given key will continue to be part of the dataset.
     *
     * @param key
     * @return Integer reply, returns the remaining time to live in seconds of a key that has an
     * EXPIRE. In Redis 2.6 or older, if the Key does not exists or does not have an
     * associated expire, -1 is returned. In Redis 2.8 or newer, if the Key does not have an
     * associated expire, -1 is returned or if the Key does not exists, -2 is returned.
     */
    public Long ttl(final String key) {
        return redisOperationProxy.getOperation().ttl(key);
    }

    /**
     * Set a timeout on the specified key. After the timeout the key will be automatically deleted by
     * the server. A key with an associated timeout is said to be volatile in Redis terminology.
     * <p>
     * Voltile keys are stored on disk like the other keys, the timeout is persistent too like all the
     * other aspects of the dataset. Saving a dataset containing expires and stopping the server does
     * not stop the flow of time as Redis stores on disk the time when the key will no longer be
     * available as Unix time, and not the remaining seconds.
     * <p>
     * Since Redis 2.1.3 you can update the value of the timeout of a key already having an expire
     * set. It is also possible to undo the expire at all turning the key into a normal key using the
     * {@link #persist(String) PERSIST} command.
     * <p>
     * Time complexity: O(1)
     *
     * @param key
     * @param seconds
     * @return Integer reply, specifically: 1: the timeout was set. 0: the timeout was not set since
     * the key already has an associated timeout (this may happen only in Redis versions &lt;
     * 2.1.3, Redis &gt;= 2.1.3 will happily update the timeout), or the key does not exist.
     * @see <a href="http://code.google.com/p/redis/wiki/ExpireCommand">ExpireCommand</a>
     */
    public Long expire(final String key, final int seconds) {
        return redisOperationProxy.getOperation().expire(key, seconds);
    }

    /**
     * Atomically renames the key oldkey to newkey. If the source and destination name are the same an
     * error is returned. If newkey already exists it is overwritten.
     * <p>
     * Time complexity: O(1)
     *
     * @param oldkey
     * @param newkey
     * @return Status code repy
     */
    public String rename(final String oldkey, final String newkey) {
        return redisOperationProxy.getOperation().rename(oldkey, newkey);
    }

    /**
     * Sort a Set or a List.
     * <p>
     * Sort the elements contained in the List, Set, or Sorted Set value at key. By default sorting is
     * numeric with elements being compared as double precision floating point numbers. This is the
     * simplest form of SORT.
     *
     * @param key
     * @return Assuming the Set/List at key contains a list of numbers, the return value will be the
     * list of numbers ordered from the smallest to the biggest number.
     * @see #sort(String, String)
     * @see #sort(String, SortingParams)
     * @see #sort(String, SortingParams, String)
     */
    public List<String> sort(final String key) {
        return redisOperationProxy.getOperation().sort(key);
    }

    /**
     * Sort a Set or a List accordingly to the specified parameters.
     * <p>
     * <b>examples:</b>
     * <p>
     * Given are the following sets and key/values:
     *
     * <pre>
     * x = [1, 2, 3]
     * y = [a, b, c]
     *
     * k1 = z
     * k2 = y
     * k3 = x
     *
     * w1 = 9
     * w2 = 8
     * w3 = 7
     * </pre>
     * <p>
     * Sort Order:
     *
     * <pre>
     * sort(x) or sort(x, sp.asc())
     * -&gt; [1, 2, 3]
     *
     * sort(x, sp.desc())
     * -&gt; [3, 2, 1]
     *
     * sort(y)
     * -&gt; [c, a, b]
     *
     * sort(y, sp.alpha())
     * -&gt; [a, b, c]
     *
     * sort(y, sp.alpha().desc())
     * -&gt; [c, a, b]
     * </pre>
     * <p>
     * Limit (e.g. for Pagination):
     *
     * <pre>
     * sort(x, sp.limit(0, 2))
     * -&gt; [1, 2]
     *
     * sort(y, sp.alpha().desc().limit(1, 2))
     * -&gt; [b, a]
     * </pre>
     * <p>
     * Sorting by external keys:
     *
     * <pre>
     * sort(x, sb.by(w*))
     * -&gt; [3, 2, 1]
     *
     * sort(x, sb.by(w*).desc())
     * -&gt; [1, 2, 3]
     * </pre>
     * <p>
     * Getting external keys:
     *
     * <pre>
     * sort(x, sp.by(w*).get(k*))
     * -&gt; [x, y, z]
     *
     * sort(x, sp.by(w*).get(#).get(k*))
     * -&gt; [3, x, 2, y, 1, z]
     * </pre>
     *
     * @param key
     * @param sortingParameters
     * @return a list of sorted elements.
     * @see #sort(String)
     * @see #sort(String, SortingParams, String)
     */
    public List<String> sort(final String key, final SortingParams sortingParameters) {
        return redisOperationProxy.getOperation().sort(key, sortingParameters);
    }

    /**
     * Sort a Set or a List and Store the Result at dstkey.
     * <p>
     * Sort the elements contained in the List, Set, or Sorted Set value at key and store the result
     * at dstkey. By default sorting is numeric with elements being compared as double precision
     * floating point numbers. This is the simplest form of SORT.
     *
     * @param key
     * @param dstkey
     * @return The number of elements of the list at dstkey.
     * @see #sort(String)
     * @see #sort(String, SortingParams)
     * @see #sort(String, SortingParams, String)
     */
    public Long sort(final String key, final String dstkey) {
        return redisOperationProxy.getOperation().sort(key, dstkey);
    }

    /**
     * Sort a Set or a List accordingly to the specified parameters and store the result at dstkey.
     *
     * @param key
     * @param sortingParameters
     * @param dstkey
     * @return The number of elements of the list at dstkey.
     * @see #sort(String, SortingParams)
     * @see #sort(String)
     * @see #sort(String, String)
     */
    public Long sort(final String key, final SortingParams sortingParameters, final String dstkey) {
        return redisOperationProxy.getOperation().sort(key, sortingParameters, dstkey);
    }

    /**
     * Add the specified member to the set value stored at key. If member is already a member of the
     * set no operation is performed. If key does not exist a new set with the specified member as
     * sole member is created. If the key exists but does not hold a set value an error is returned.
     * <p>
     * Time complexity O(1)
     *
     * @param key
     * @param members
     * @return Integer reply, specifically: 1 if the new element was added 0 if the element was
     * already a member of the set
     */
    public Long sadd(final String key, final String... members) {
        return redisOperationProxy.getOperation().sadd(key, members);
    }

    /**
     * Return all the members (elements) of the set value stored at key. This is just syntax glue for
     * {@link #sinter(String...) SINTER}.
     * <p>
     * Time complexity O(N)
     *
     * @param key
     * @return Multi bulk reply
     */
    public Set<String> smembers(final String key) {
        return redisOperationProxy.getOperation().smembers(key);
    }

    /**
     * 删除set成员
     *
     * @param key
     * @param members
     * @return
     */
    public Long srem(final String key, final String... members) {
        return redisOperationProxy.getOperation().srem(key, members);
    }

    /**
     * Remove a random element from a Set returning it as return value. If the Set is empty or the key
     * does not exist, a nil object is returned.
     * <p>
     * The {@link #srandmember(String)} command does a similar work but the returned element is not
     * removed from the Set.
     * <p>
     * Time complexity O(1)
     *
     * @param key
     * @return Bulk reply
     */
    public String spop(final String key) {
        return redisOperationProxy.getOperation().spop(key);
    }

    /**
     * Remove a random element from a Set returning it as return value. If the Set is empty or the key
     * does not exist, a nil object is returned.
     * <p>
     * The {@link #srandmember(String)} command does a similar work but the returned element is not
     * removed from the Set.
     * <p>
     * Time complexity O(1)
     *
     * @param key
     * @return Bulk reply
     */
    public <T> T spop(final String key, final Class<T> clazz) {
        validate(clazz);
        T t = null;
        String text = spop(key);
        if (StringUtils.isNotBlank(text)) {
            t = JsonUtils.getClazz(text, clazz);
        }
        return t;
    }

    /**
     * Return 1 if member is a member of the set stored at key, otherwise 0 is returned.
     * <p>
     * Time complexity O(1)
     *
     * @param key
     * @param member
     * @return Integer reply, specifically: 1 if the element is a member of the set 0 if the element
     * is not a member of the set OR if the key does not exist
     */
    public Boolean sismember(final String key, final String member) {
        return redisOperationProxy.getOperation().sismember(key, member);
    }

    private <T> void validate(T t) {
        if (t == null)
            throw new UnexpectedException("empty object");
        Class<T> clazz = (Class<T>) t.getClass();
        if (clazz == String.class || clazz == List.class || clazz == Map.class)
            throw new UnexpectedException("not support method");
    }

    private <T> void keyValidate(Class<T> clazz) {
        if (clazz == null || clazz == List.class || clazz == Map.class)
            throw new UnexpectedException("not support method");
    }

    private <T> void valueValidate(Class<T> clazz) {
        if (clazz == null || clazz == List.class || clazz == Map.class)
            throw new UnexpectedException("not support method");
    }

    private <T> void validate(Class<T> clazz) {
        if (clazz == String.class || clazz == List.class || clazz == Map.class)
            throw new UnexpectedException("not support method");
    }

    private <T> void validate(T... ts) {
        if (ArrayUtils.isEmpty(ts))
            throw new UnexpectedException("empty array");
    }

    private <T> void validate(List<T> list) {
        if (CollectionUtils.isEmpty(list))
            throw new UnexpectedException("empty list");
    }

    private <T> void validate(Set<T> set) {
        if (CollectionUtils.isEmpty(set))
            throw new UnexpectedException("emptr set");
    }

    private <K, T> void validate(Map<K, T> map) {
        if (map == null || map.isEmpty())
            throw new UnexpectedException("emptr map");
    }

    private void validate(String str) {
        if (StringUtils.isBlank(str))
            throw new UnexpectedException("emptr str");
    }

    private <T> String[] generate(T... ts) {
        int tsSize = ts.length;
        T t = null;
        String value = null;
        String[] array = new String[0];
        for (int i = 0; i < tsSize; i++) {
            t = ts[i];
            if (!(t.getClass().equals(String.class)))
                value = JsonUtils.obj2String(t);
            else
                value = (String) t;
            array = (String[]) ArrayUtils.add(array, value);
        }
        return array;
    }

    private <T> String[] generate(List<T> list) {
        int size = list.size();
        T t = null;
        String value = null;
        String[] array = new String[0];
        for (int i = 0; i < size; i++) {
            t = list.get(i);
            if (!(t.getClass().equals(String.class)))
                value = JsonUtils.obj2String(t);
            else
                value = (String) t;
            array = (String[]) ArrayUtils.add(array, value);
        }
        return array;
    }


    public String rpop(final String key) {
        return redisOperationProxy.getOperation().rpop(key);
    }

    public List<String> srandmember(String key, int count) {
        return redisOperationProxy.getOperation().srandmember(key, count);
    }

    public String getSet(final String key, final String value) {
        return redisOperationProxy.getOperation().getSet(key, value);
    }

    public Boolean setbit(String key, long offset, boolean value) {
        return redisOperationProxy.getOperation().setbit(key, offset, value);
    }

    public Boolean setbit(String key, long offset, String value) {
        return redisOperationProxy.getOperation().setbit(key, offset, value);
    }

    public Long bitcount(final String key) {
        return redisOperationProxy.getOperation().bitcount(key);
    }

    public Long bitcount(final String key, long start, long end) {
        return redisOperationProxy.getOperation().bitcount(key, start, end);
    }
}